package org.bichko.cydot;

import static java.lang.System.*;
import static org.bichko.cydot.DotParser.Symbol.*;
import cytoscape.*;
import cytoscape.data.*;
import java.io.*;
import java.util.*;

// TODO edge from/to subgraph
// TODO newrec -> ';'
// TODO organize setter
public class DotParser extends AbstractParser {

    public enum Symbol {
        GRAPH, DIGRAPH, SUBGRAPH, STRICT, NODE, EDGE, EDGEOP_UND, EDGEOP_DIR, BRACE_OPEN, BRACE_CLOSE, BRACKET_OPEN, BRACKET_CLOSE, COMMA, EQUALS, TEXT, NEWREC, EOF;
    };

    private final Deque<String> graphs = new LinkedList<String>();
    boolean strict = false;

    private final Map<String, CyNode> nodes = new LinkedHashMap<String, CyNode>();
    private final Map<String, CyEdge> edges = new LinkedHashMap<String, CyEdge>();

    private String id = null;
    private String key = null;
    private CyNode curNode = null;
    private CyEdge curEdge = null;

    private final Setter attrGraph = new CySetter(Cytoscape
            .getNetworkAttributes());
    private final Setter attrNodes = new CySetter(Cytoscape.getNodeAttributes());
    private final Setter attrEdges = new CySetter(Cytoscape.getEdgeAttributes());
    private Setter attr = new Setter();

    public DotParser(Reader input) {
        super(input);
    }

    public Map<String, CyNode> getNodes() {
        return nodes;
    }

    public Map<String, CyEdge> getEdges() {
        return edges;
    }

    private CyNode getNode(String id) {
        CyNode node = nodes.get(id);
        if (node == null) {
            nodes.put(id, node = Cytoscape.getCyNode(id, true));
            out.println("NEW NODE: " + id);
        }
        return node;
    }

    // TODO directionality
    private CyEdge getEdge(CyNode n1, CyNode n2) {
        CyEdge edge = edges.get(n1.getIdentifier() + ":" + n2.getIdentifier());
        if (edge == null)
            edges.put(n1.getIdentifier() + ":" + n2.getIdentifier(),
                    edge = Cytoscape.getCyEdge(n1, n2, Semantics.INTERACTION,
                            "int", true));
        return edge;
    }

    protected void curClear() {
        id = null;
        key = null;
        curNode = null;
        curEdge = null;
    }

    protected void parseRoot() {
        while (next() != null) {
            if (symbol == STRICT)
                strict = true;
            else if (symbol == GRAPH || symbol == DIGRAPH)
                parseGraph(true);
            else if (symbol == NEWREC)
                curClear();
            else if (symbol == EOF)
                return;
            else
                error();
        }
    }

    private void parseGraph(boolean checkName) {
        curClear();
        if (checkName && next() == TEXT) {
            out.println("GRAPH NAME: " + text);
            next();
        }
        assertState(BRACE_OPEN);
        while (next() != BRACE_CLOSE) {

            if (id != null) {
                if (symbol == EQUALS) {
                    key = id;
                } else {
                    curNode = getNode(id);
                    attr = attrNodes.change(id);
                }
                id = null;
            }

            if (symbol == SUBGRAPH) {
                parseGraph(true);
            } else if (symbol == BRACE_OPEN) {
                parseGraph(false);
            } else if (symbol == TEXT) {
                id = text;
            } else if (symbol == EQUALS) {
                assertNext(TEXT);
                attr.set(key, text);
            } else if (symbol == EDGEOP_UND || symbol == EDGEOP_DIR) {
                if (curNode == null)
                    error();
                assertNext(TEXT);
                out.println("NEW EDGE: " + curNode.getIdentifier() + " --> "
                        + text);
                CyNode to = getNode(text);
                curEdge = getEdge(curNode, to);
                curNode = to;
                attr = attrEdges.change(curEdge.getIdentifier());
            } else if (symbol == GRAPH || symbol == NODE || symbol == EDGE) {
                assertNext(BRACKET_OPEN);
                parseAttrs();
            } else if (symbol == BRACKET_OPEN) {
                if (curNode == null)
                    error();
                parseAttrs();
            } else if (symbol == NEWREC) {
                curClear();
            } else {
                error();
            }
        }
        curClear();
    }

    private void parseAttrs() {
        while (next() != BRACKET_CLOSE) {
            if (symbol == TEXT) {
                attr.set(key, "true");
                key = text;
            } else if (key != null && symbol == EQUALS) {
                assertNext(TEXT);
                attr.set(key, text);
                key = null;
            } else if (symbol == COMMA) {

            } else {
                error();
            }
        }
        attr.set(key, "true");
        out.println();
    }

    private static class Setter {

        void set(String key, String val) {
            if (key != null)
                out.println("ASSIGNING: " + key + " == " + val);
        }

        public Setter change(String id) {
            return this;
        }
    }

    private static class CySetter extends Setter {

        private final CyAttributes attrs;
        private String id = null;

        public CySetter(CyAttributes attrs) {
            this.attrs = attrs;
        }

        public CySetter change(String id) {
            this.id = id;
            return this;
        }

        void set(String key, String val) {
            if (key != null)
                attrs.setAttribute(id, key, val);
        }

    }
}
